/* FILE : unisocket.c */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>

#include "unisocket.h"

#define DEFAULT_LISTEN_PORT 8000
#define UNISOCKET_BUFFER_LENGTH 8192

static char *local_host = "127.0.0.1";
static int listen_port = DEFAULT_LISTEN_PORT;
static int socket_descriptor;
static UnisocketReceiveHandler rec_handler;
static int pipes[2];
static pid_t child = -1;

static int already_start = 0;

static void unisocket_send_buffer(void *, size_t);
static void socket_loop(int, int);
static void pipe_read(int);
static void pipe_write(int, char *, int);
static void on_receive_loop_exit(int);

int
unisocket_connect()
{
	if (already_start)
		return 0;
	already_start = 1;
	if (pipe(pipes) == -1) {
#if DEBUG
		fprintf(stderr, "UNISOCKET : create pipes failed!\n");
#endif
		close(socket_descriptor);
		return 0;
	}
	child = fork();

	if (child < 0) {
#if DEBUG
		fprintf(stderr, "UNISOCKET : fork for receive loop failed!\n");
#endif
		return 0;
	} else if (child == 0) {
		signal(SIGUSR1, pipe_read);
		unisocket_set_receive_handler(unisocket_send_buffer);
		socket_loop(pipes[0], pipes[1]);
		exit(1);
	} else {
		signal(SIGUSR1, pipe_read);
		signal(SIGCHLD, on_receive_loop_exit);
	}
	return 1;
}

void
unisocket_disconnect()
{
	close(pipes[0]);
	close(pipes[1]);
	close(socket_descriptor);
}

void
unisocket_send(char *buf, int len)
{
	pipe_write(pipes[1], buf, len);
}

void
unisocket_set_receive_handler(UnisocketReceiveHandler handler)
{
	rec_handler = handler;
}

static void
unisocket_send_buffer(void *buf, size_t buf_size)
{
	send(socket_descriptor, (char *)buf, (int)buf_size, 0);
}

void
socket_loop(int read_pipe, int write_pipe)
{
	struct sockaddr_in pin;
	struct hostent *server_host_name;
	char buf[UNISOCKET_BUFFER_LENGTH];

	if ((server_host_name = gethostbyname(local_host)) == 0) {
#if DEBUG
		fprintf(stderr, "UNISOCKET : resolving host name error\n");
#endif
		return;
	}

	bzero(&pin, sizeof(pin));
	pin.sin_family = AF_INET;
	pin.sin_addr.s_addr = htonl(INADDR_ANY);
	pin.sin_addr.s_addr = ((struct in_addr *)
			(server_host_name->h_addr))->s_addr;
	pin.sin_port = htons(listen_port);

	if ((socket_descriptor = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
#if DEBUG
		fprintf(stderr, "UNISOCKET : create socket failed!\n");
#endif
		return;
	}

	if (connect(socket_descriptor, (void*)&pin, sizeof(pin)) == -1) {
#if DEBUG
		fprintf(stderr, "UNISOCKET : connect to xaux server failed!\n");
#endif
		return;
	}

#if DEBUG
	fprintf(stderr, "UNISOCKET : connect server success and waiting \
			for messages from server\n");
#endif
	while(1) {
		if (recv(socket_descriptor, buf, UNISOCKET_BUFFER_LENGTH, 0) == -1) {
#if DEBUG
			fprintf(stderr, "UNISOCKET : receive message error!\n");
#endif
			continue;
		} else {
#if DEBUG
			fprintf(stderr, "UNISOCKET : receive message ... ...\n");
#endif
		}
		kill(getppid(), SIGUSR1);
		pipe_write(write_pipe, buf, UNISOCKET_BUFFER_LENGTH);
	}
}

static void
pipe_read(int signo)
{
	char buf[UNISOCKET_BUFFER_LENGTH];
	int len = 0;

	read(pipes[0], &len, sizeof(int));

	read(pipes[0], buf, len);

	if (rec_handler)
		(*rec_handler)(buf, len);
}

static void
pipe_write(int pipe, char *buf, int len)
{
	kill(getppid(), SIGUSR1);

	write(pipe, &len, sizeof(int));

	write(pipe, buf, len);
	return;
}

static void
on_receive_loop_exit(int signo)
{
	waitpid(child, NULL, 0);
	child = -1;
}
